-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(nextjs): Improve pageload transaction creation #5574
Conversation
size-limit report 📦
|
Does this have the chance to change certain transaction names? |
@AbhiPrasad I'm not 100% sure but I think it's unlikely that it does. |
interface SentryEnhancedNextData extends NextData { | ||
// contains props returned by `getInitialProps` - except for `pageProps`, these are the props that got returned by `getServerSideProps` or `getStaticProps` | ||
props: { | ||
_sentryGetInitialPropsTraceData?: string; // trace id, if injected by server-side `getInitialProps` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not just trace id - let's refer to it as trace parent info since it has sampled flag and parent span id as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 22a0f83
if (startTransactionOnPageLoad) { | ||
const { route, traceParentData, baggage, params } = extractNextDataTagInformation(); | ||
|
||
prevTransactionName = route || global.document.location.pathname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the change to global.document.location
? Let's not change for the sake for change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was just easier to test. I generally do not change stuff for the sake of changing them. -> 651e8e0
baggage: string | undefined; | ||
params: ParsedUrlQuery | undefined; | ||
} { | ||
let nextData: SentryEnhancedNextData | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
low: IMO I would like this function to act as a builder, perhaps something like so:
const nextDataTagInfo = {};
if (nextData) {
nextDataTagInfo.route = nextData.page;
nextDataTagInfo.params = nextData.query;
if (nextData.props) {
// ...
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tried this. Trust me, it's uglier than it is right now + you'll get problems with typing. I'm leaving it as is. Mutability is the root of all evil. Feel free to push changes to this if you want though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with 1bde09a, let me know what you think! (feel free to revert if you think it'll cause more bugs than less).
/** | ||
* Describes data located in the __NEXT_DATA__ script tag. This tag is present on every page of a Next.js app. | ||
*/ | ||
interface SentryEnhancedNextData extends NextData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there other fields in NextData that we can use? https://github.com/vercel/next.js/blob/0796b6faa991cd58a517b0f160320e9978208066/packages/next/shared/lib/utils.ts#L84-L109 - perhaps create a follow up issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Poked around in the type. Nothing really sticks out as very useful. I can create an issue but very low prio imo.
{ | ||
"props": ${JSON.stringify(pageProperties.props)}, | ||
"page": "${pageProperties.route}", | ||
"query": ${JSON.stringify(pageProperties.query)}, | ||
"buildId": "y76hvndNJBAithejdVGLW", | ||
"isFallback": false, | ||
"gssp": true, | ||
"appGip": true, | ||
"scriptLoader": [] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Prefer if we create an object and stringify it for readability + we can type with the type exported from nextjs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 bec58e0
name: prevTransactionName, | ||
op: 'pageload', | ||
tags: DEFAULT_TAGS, | ||
...(params && { data: params }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iirc setting data does nothing for transactions - can we throw it into some other part of the event? Like a context?
Does this have PII data concerns? Perhaps we guard this with sendDefaultPII
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally found a use for that flag! Yup there are potential pii issues. Added the flag but removed tests around data because that's just not worth the effort of mocking the hub/client/options. 0ba0d6e
Btw, this data shows up in the trace details when you open a transaction:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, this data shows up in the trace details when you open a transaction:
Ah yeah - that means it'll show up on linked errors as well (since we copy over the entire trace context). I guess since we don't index this we can leave it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such a good change! Good idea on the approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good!
Small suggestion on your PR description (second bullet point):
- Additionally, this PR lays the groundwork for starting pageload traces on the server and continuing them on the client (as opposed to just starting them on the client), by adding a function to retrieve traceparent data and baggage from the
__NEXT_DATA__
tag. (Work to set those values will happen in a future PR.)
As per usual, pretty much all of my comments are about explaining stuff. IMHO the code itself is good to go!
_sentryGetServerSidePropsTraceData?: string; // trace parent info, if injected by server-side `getServerSideProps` | ||
_sentryGetServerSidePropsBaggage?: string; // baggage, if injected by server-side `getServerSideProps` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we're leaving getStaticProps
somewhat to the side for the moment, but do you think it's worth it to add a TODO about it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #5574 (comment)
* Every Next.js page (static and dynamic ones) comes with a script tag with the id "__NEXT_DATA__". This script tag | ||
* contains a JSON object with data that was either generated at build time for static pages (`getStaticProps`), or at | ||
* runtime with data fetchers like `getServerSideProps.`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that getStaticProps
runs an arbitrary amount of time before the page is served (both when it's run at build time and when it's run at runtime as part of revalidation), it doesn't make sense to send trace data from it, only the parameterized route. Do we want to note that here somehow? Could be in the docstring here or could be below where you're dealing with the other two, to explain why it's not all three.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finally read up on how ISR works and I think I fully understand it now. There is one case where it totally makes sense to trace getStaticProps
and I went ahead and added logic and an explanation for it: 652d3eb
|
||
if (startTransactionOnPageLoad) { | ||
const { route, traceParentData, baggage, params } = extractNextDataTagInformation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { route, traceParentData, baggage, params } = extractNextDataTagInformation(); | |
// Pull data from the `__NEXT_DATA__` script tag automatically injected serverside by nextjs | |
const { route, traceParentData, baggage, params } = extractNextDataTagInformation(); |
// Set up JSDom - needed for page load instrumentation | ||
const dom = new JSDOM(undefined, { url: 'https://example.com/' }); | ||
Object.defineProperty(global, 'document', { value: dom.window.document, writable: true }); | ||
Object.defineProperty(global, 'location', { value: dom.window.document.location, writable: true }); | ||
|
||
const originalGlobalDocument = getGlobalObject<Window>().document; | ||
const originalGlobalLocation = getGlobalObject<Window>().location; | ||
afterAll(() => { | ||
// Clean up JSDom | ||
Object.defineProperty(global, 'document', { value: originalGlobalDocument }); | ||
Object.defineProperty(global, 'location', { value: originalGlobalLocation }); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a sentence or two to the comment explaining why we need to do this?
}; | ||
|
||
const dom = new JSDOM( | ||
// Just some example what a __NEXT_DATA__ tag might look like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Just some example what a __NEXT_DATA__ tag might look like | |
// Just an example of what a __NEXT_DATA__ tag might look like |
], | ||
])( | ||
'creates a pageload transaction (#%#)', | ||
(url, route, query, props, hasNextData, expectedStartTransactionCall) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(url, route, query, props, hasNextData, expectedStartTransactionCall) => { | |
(url, route, query, props, hasNextData, expectedStartTransactionArguments) => { |
It might be nice to put this list in a comment at the top of the it.each
, and/or stick a blank line between test cases - it's pretty hard to tell what you're looking at given that it's half a dozen items per test case, and each test case goes on for a variable number of lines.
@@ -153,16 +153,11 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp | |||
tunnel?: string; | |||
|
|||
/** | |||
* Important: This option is currently unused and will only work in the next major version of the SDK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than just deleting this line, I think we should say that it works client-side in the nextjs SDK but not elsewhere.
This PR aims to improve Next.js pageload transactions:
__NEXT_DATA__
script tag Next.js provides on each page, instead of having to wait for the next router to initialize. We're pretty much guaranteed to always retrieve a parameterized route from__NEXT_DATA__
.getServerSideProps
orgetInitialProps
in place).Ref: #5505